<?php
/**
 * polwebgis
 * 
 * This file is part of polwebgis.
 *
 * @copyright   Copyright (c) 2012 Dominik Janisch (http://www.joanneum.at)
 * @lastmodified    $LastChangedDate: 2012-04-02 12:10:32 +0000 (Mon, 02 Apr 2012) $
 * @revision    $Revision: 172 $ 
 */

class Classification
{
    private $language_handler_;
    private $map_class_properties_;

    public function __construct($singleton_registry, &$map_class_properties)
    {
        $this->language_handler_ = $singleton_registry->getSingleton('LanguageHandler');
        $this->map_class_properties_ = $map_class_properties;
    }
    
    public function __destruct(){}
    
    public function getJenksBreaks($arr_data, $classes)
    {
        $arr_tmp = $this->calculateJenksBreaks($arr_data, $classes);
        $arr_return = array();
        foreach($arr_tmp as $key => $value)
        {
            $arr_return[$key]['condition'] = "cases > :from and cases <= :to";
            $arr_return[$key]['label'] = "> :from <= :to";
            $arr_return[$key]['value'] = $value;
        }
        return $arr_return;
    }
    
    public function getEqualBreaks($classes)
    {
        $arr_tmp = $this->calculateEqualBreaks($classes);
        $arr_return = array();
        foreach($arr_tmp as $key => $value)
        {
            $arr_return[$key]['condition'] = "cases >= :from and cases < :to";
            $arr_return[$key]['label'] = ":from < :to";
            $arr_return[$key]['value'] = $value;
        }
        return $arr_return;
    }
    
    public function getStddevBreaks(&$map_list)
    {
        $arr_tmp = $this->calculateStddevBreaks(&$map_list);
        $arr_return = array();
        $below_mean = true;
        $cnt = 0;
        foreach($arr_tmp as $key => $value)
        {
            if($this->map_class_properties_->getMean() == $value)
            {
                $below_mean = false;
                $arr_return[$cnt]['condition'] = "cases = :from";
                $arr_return[$cnt]['label'] = $this->language_handler_->getLanguageString('analysis_stats_mean')." (:from)";
                $arr_return[$cnt]['value'] = $value;
                ++$cnt;
            }
            if($below_mean)
            {
                $arr_return[$cnt]['condition'] = "cases >= :from and cases < :to";
                $arr_return[$cnt]['label'] = ":from < :to";
            }
            else
            {
                $arr_return[$cnt]['condition'] = "cases > :from and cases <= :to";
                $arr_return[$cnt]['label'] = ":from <= :to";
            }
            $arr_return[$cnt]['value'] = $value;
            ++$cnt;
        }
        return $arr_return;
    }
    
    public function getBoxMapBreaks($arr_data, &$map_list, $hinge, $hide_values)
    {
        //var_dump($hide_values);
        $this->map_class_properties_->setClassWidth(4);
        $arr_quartile_breaks = array();
        $sum = 0;
        foreach($this->getQuantileBreaks(4) as $value)
        {
            $sum += $value;
            $arr_quartile_breaks[] = $sum;
        }
        $lower_border = $arr_data[$arr_quartile_breaks[0]];
        $upper_border = $arr_data[$arr_quartile_breaks[2]-1];
        $iqr = $upper_border-$lower_border; 
        $arr_return = array();
        $cnt=0;
        $arr_return[$cnt]['condition'] = "cases < :from";
        if($hide_values)
            $arr_return[$cnt]['label'] = $this->language_handler_->getLanguageString('lower_outlier');
        else
            $arr_return[$cnt]['label'] = $this->language_handler_->getLanguageString('lower_outlier')." ( < :from)";
        $arr_return[$cnt]['value'] = $lower_border-$iqr*$hinge;
        ++$cnt;
        $arr_return[$cnt]['condition'] = "cases > :from and cases <= :to";
        if($hide_values)
            $arr_return[$cnt]['label'] = "< 25%";
        else
            $arr_return[$cnt]['label'] = "< 25% (:from <= :to)";
        $arr_return[$cnt]['value'] = $lower_border-$iqr*$hinge;
        
        $arr_return[++$cnt]['condition'] = "cases > :from and cases <= :to";
        if($hide_values)
            $arr_return[$cnt]['label'] = "25-50%";
        else
            $arr_return[$cnt]['label'] = "25-50% (:from <= :to)";
        $arr_return[$cnt]['value'] = $arr_data[$arr_quartile_breaks[0]-1];
        
        $arr_return[++$cnt]['condition'] = "cases > :from and cases <= :to";
        if($hide_values)
            $arr_return[$cnt]['label'] = "50-75%";
        else
            $arr_return[$cnt]['label'] = "50-75% (:from <= :to)";
        $arr_return[$cnt]['value'] = $arr_data[$arr_quartile_breaks[1]-1];
        
        $arr_return[++$cnt]['condition'] = "cases > :from and cases <= :to";
        if($hide_values)
            $arr_return[$cnt]['label'] = " > 75%";
        else
            $arr_return[$cnt]['label'] = " > 75% (:from <= :to)";
        $arr_return[$cnt++]['value'] = $arr_data[$arr_quartile_breaks[2]-1];    
            
        $arr_return[$cnt]['condition'] = "cases > :from";
        if($hide_values)
            $arr_return[$cnt]['label'] = $this->language_handler_->getLanguageString('upper_outlier');
        else
            $arr_return[$cnt]['label'] = $this->language_handler_->getLanguageString('upper_outlier')." ( > :from)";
        $arr_return[$cnt]['value'] = $upper_border+$iqr*$hinge;
        ++$cnt;
        $arr_return[$cnt]['condition'] = "cases > :from";
        $arr_return[$cnt]['label'] = " > :from";
        $arr_return[$cnt]['value'] = $upper_border+$iqr*$hinge;
        $map_list->set('class_width', 6);
        return $arr_return;
          
    }
    
    public function getPercentileBreaks(&$map_list)
    {
        $arr_return = array();
        $sum = 0;
        $arr_return[0] = round($this->map_class_properties_->getCases()/100);
        $sum += $arr_return[0]; 
        $arr_return[1] = round($this->map_class_properties_->getCases()*0.09);
        $sum += $arr_return[1];
        $arr_return[2] = round($this->map_class_properties_->getCases()*0.4);
        $sum += $arr_return[2];
        $arr_return[3] = round($this->map_class_properties_->getCases()*0.4);
        $sum += $arr_return[3];
        $arr_return[4] = round($this->map_class_properties_->getCases()*0.09);
        $sum += $arr_return[4];
        $arr_return[5] = ceil($this->map_class_properties_->getCases()/100);
        $sum += $arr_return[5];
        $map_list->set('class_width', 6);
        return $arr_return;
    }
    
    public function getQuantileBreaks($classes)
    {
        $arr_return = array();
        $offset = 0;
        for($cnt=0; $cnt<$classes; ++$cnt)
        {
            $arr_return[$cnt] = round(($cnt+1)*$this->map_class_properties_->getAvgWidthFloat())-$offset;
            $offset = round(($cnt+1)*$this->map_class_properties_->getAvgWidthFloat());
        }
        return $arr_return;
    }
    
    // Jenks Optimal (Natural Breaks) algorithm
    // Based on the Jenks algorithm from the QGIS implementation by Carson Farmer (http://www.carsonfarmer.com/)
    // QGIS sourcefile: qgsgraduatedsymbolrendererv2.cpp
    private function calculateJenksBreaks($arr_data, $classes)
    {
        $sample = $arr_data;
        //$sample = array(1, 100, 150, 500, 6300, 6500, 6700, 12400); 
        $classes = $classes;
        $maximum = PHP_INT_MAX;
        $n = count($sample);
        
        $matrixOne = array();
        $matrixTwo = array();
        
        for($i = 0; $i < $n + 1; ++$i)
        {
            $matrixOne[$i] = array_fill(0, $classes+1, 0.0);
            $matrixTwo[$i] = array_fill(0, $classes+1, 0.0);
        }
        for($i = 1; $i < $classes + 1; ++$i)
        {
            $matrixOne[1][$i] = 1.0;
            $matrixTwo[1][$i] = 0.0;
            for($j = 2; $j < $n + 1; ++$j)
            {
              $matrixTwo[$j][$i] = $maximum;
            }
        }
        $v = 0.0;
        for($l = 2; $l < $n + 1; ++$l)
        {
            $s1 = 0.0;
            $s2 = 0.0;
            $w = 0.0;
            for ($m = 1; $m < $l + 1; ++$m)
            {
              $i3 = $l - $m + 1;
            
              $val = $sample[ $i3 - 1 ];
            
              $s2 += $val * $val;
              $s1 += $val;
            
              $w += 1.0;
              $v = $s2 - ( $s1 * $s1 ) / $w;
              $i4 = $i3 - 1;
            
              if($i4 != 0.0)
              {
                for($j = 2; $j < $classes + 1; ++$j)
                {
                  if($matrixTwo[$l][$j] >= $v + $matrixTwo[$i4][$j - 1])
                  {
                    $matrixOne[$l][$j] = $i3;
                    $matrixTwo[$l][$j] = $v + $matrixTwo[$i4][$j - 1];
                  }
                }
              }
            }
            $matrixOne[$l][1] = 1.0;
            $matrixTwo[$l][1] = $v;
        }
        
        $breaks = array_fill(0, $classes+1, 0.0);
        $this->map_class_properties_->setJenksMin();
        $breaks[$classes] = $this->map_class_properties_->getMax();
        $breaks[0] = $this->map_class_properties_->getMin();
        $k = $n;
        $count = $classes;
        while($count >= 2)
        {
            $id = ($matrixOne[$k][$count] - 2) >= 0 ? $matrixOne[$k][$count] - 2 : 0 ;
            $breaks[$count - 1] = $sample[$id];
            $k = $matrixOne[$k][$count] - 1;
            $count -= 1;
        }
        //var_dump($breaks);
        return $breaks;
    }
    
    private function calculateStddevBreaks(&$map_list)
    {
        $min = floor(($this->map_class_properties_->getMin()-$this->map_class_properties_->getMean())/$this->map_class_properties_->getStddev());
        $max = ceil(($this->map_class_properties_->getMax()-$this->map_class_properties_->getMean())/$this->map_class_properties_->getStddev());
        $map_list->set('class_width', $max-$min+1);
        $this->map_class_properties_->setClassWidth($map_list->get('class_width'));
        $this->map_class_properties_->setWidth();
        $tmp_start = $this->map_class_properties_->getMean()+$min*$this->map_class_properties_->getStddev();
        $tmp_end = $tmp_start + $this->map_class_properties_->getStddev();
        $arr_limits = array();
        $arr_limits[] = $tmp_start;
        for($min; $min < $max; ++$min)
        {
            $arr_limits[] = $tmp_end;
            $tmp_end += $this->map_class_properties_->getStddev();
        }
        return $arr_limits;
    }
    
    private function calculateEqualBreaks($classes)
    {
        $tmp_start = $this->map_class_properties_->getMin();
        $tmp_end = $tmp_start + $this->map_class_properties_->getWidth();
        $arr_limits = array();
        $arr_limits[] = $tmp_start;
        for($cnt=0; $cnt < $classes; ++$cnt)
        {
            $arr_limits[] = $tmp_end;
            $tmp_end += $this->map_class_properties_->getWidth();
        }
        return $arr_limits;
    }
}
?>
